import { ArgumentsHost, Catch, HttpException, HttpStatus, Logger } from "@nestjs/common";
import { BaseExceptionFilter } from "@nestjs/core";
import { unlink } from "fs/promises";
import { join, resolve } from "path";

import { UPLOAD_FOLDER } from "../config/config-data";
import AjaxResult from "../utils/AjaxResult";

@Catch()
export class GlobalException extends BaseExceptionFilter {
    private readonly logger = new Logger(GlobalException.name);

    /**
     * 处理全局异常
     * @param exception 异常对象
     * @param host 主机对象
     * @returns 无
     */
    catch(exception: any, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const request = ctx.getRequest();
        const response = ctx.getResponse();

        // 获取异常信息
        let status = HttpStatus.INTERNAL_SERVER_ERROR;
        let message = "Internal server error";
        let errorName = "UnknownError";

        // 处理 HttpExceptions 异常
        if (exception instanceof HttpException) {
            status = exception.getStatus();
            const exResponse: any = exception.getResponse();
            errorName = exception.constructor.name;

            // 处理文件上传异常 (因为DTO等异常，文件依旧会被上传到磁盘，因此业务出错后需要手动删除文件)
            if (
                (request.url.includes("storage/upload") && request.method === "POST" && request.files) ||
                request.file
            ) {
                batchDeleteFiles(request.files || [request.file]);
            }

            message = typeof exResponse === "string" ? exResponse : exResponse.message;
            this.logger.debug(AjaxResult.fail(status, message, request.url, exResponse.error));
            // return response.status(status).send(AjaxResult.fail(status, message, request.url, exResponse.error));
            // 不设置 http 状态码，只在 response 中设置业务状态码
            return response.send(AjaxResult.fail(status, message, request.url, exResponse.error));
        } else if (exception instanceof Error) {
            message = exception.message;
            errorName = exception.constructor.name;
        }

        // 记录错误日志
        // this.logger.error(exception.message, exception.stack);
        // this.logger.error(exception); // 传入异常对象，会自动识别异常对象的 message 和 stack 属性
        // 扩展 logger.error 方法的 stack 参数，加入请求的元数据信息。日志 transporter 可以根据此信息进行日志格式化，保存到日志文件中
        this.logger.error(message, {
            reqId: request.id || "N/A",
            errorName,
            statusCode: status,
            path: request.url,
            method: request.method,
            body: request.body,
            ip: request.ip,
            userAgent: request.get("user-agent"),
            stack: exception?.stack
        });
        // if (!IS_PROD_ENV) console.log("exception", exception);

        // 处理其他异常
        // return response.status(500).send(AjaxResult.fail(500, "Internal Server Error", request.url));
        return response.status(500).send(AjaxResult.fail(status, message, request.url, errorName));
    }
}

/**
 * 批量删除文件
 * @param files 文件数组
 * @returns 无
 */
async function batchDeleteFiles(files: Express.Multer.File[]) {
    const dest = resolve(process.cwd(), UPLOAD_FOLDER);
    const deletePromises = files.map(async file => {
        const category = file.filename.split(".")[0].split("-").pop(); // 从文件名中提取分类
        const fullPath = join(dest, category, file.filename);
        try {
            await unlink(fullPath);
            return { success: true, file };
        } catch (error) {}
    });
    await Promise.all(deletePromises);
}
